Propagate Composable requirement to implementors of lambda interfaces. Propagates requirement of composable invoke operator override to classes and interfaces that are extending lambda interfaces with corresponding annotations. Test: ComposableDeclarationCheckerTests Change-Id: Id2757562f10a2f0da198c7cce602d8ea8726b809
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt index 3239060..07aa897 100644 --- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt +++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableDeclarationCheckerTests.kt
@@ -286,4 +286,74 @@ """ ) } + + @Test + fun testOverrideComposableLambda() { + check( + """ + import androidx.compose.runtime.Composable + + class Impl : @Composable () -> Unit { + @Composable + override fun invoke() {} + } + """ + ) + } + + @Test + fun testTransitiveOverrideComposableLambda() { + check( + """ + import androidx.compose.runtime.Composable + + interface ComposableFunction : @Composable () -> Unit + + class Impl : ComposableFunction { + @Composable + override fun invoke() {} + } + """ + ) + } + + @Test + fun testMissingOverrideComposableLambda() { + check( + """ + import androidx.compose.runtime.Composable + + class Impl : @Composable () -> Unit { + <!CONFLICTING_OVERLOADS!>override fun invoke()<!> {} + } + """ + ) + } + + @Test + fun testWrongOverrideLambda() { + check( + """ + import androidx.compose.runtime.Composable + + class Impl : () -> Unit { + <!CONFLICTING_OVERLOADS!>@Composable override fun invoke()<!> {} + } + """ + ) + } + + @Test + fun testMultipleOverrideLambda() { + check( + """ + import androidx.compose.runtime.Composable + + class Impl : () -> Unit, @Composable (Int) -> Unit { + <!CONFLICTING_OVERLOADS!>@Composable override fun invoke()<!> {} + @Composable override fun invoke(p0: Int) {} + } + """ + ) + } } diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt index c0aad2e..2cc17dc 100644 --- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt +++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableDeclarationChecker.kt
@@ -22,9 +22,11 @@ import androidx.compose.compiler.plugins.kotlin.ComposeErrors.COMPOSABLE_SUSPEND_FUN import androidx.compose.compiler.plugins.kotlin.ComposeErrors.COMPOSABLE_VAR import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.builtins.isFunctionType import org.jetbrains.kotlin.builtins.isSuspendFunctionType import org.jetbrains.kotlin.container.StorageComponentContainer import org.jetbrains.kotlin.container.useInstance +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.descriptors.Modality @@ -41,6 +43,7 @@ import org.jetbrains.kotlin.resolve.checkers.DeclarationChecker import org.jetbrains.kotlin.resolve.checkers.DeclarationCheckerContext import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.supertypes import org.jetbrains.kotlin.util.OperatorNameConventions class ComposableDeclarationChecker : DeclarationChecker, StorageComponentContainerContributor { @@ -79,7 +82,22 @@ val hasComposableAnnotation = descriptor.hasComposableAnnotation() if (descriptor.overriddenDescriptors.isNotEmpty()) { val override = descriptor.overriddenDescriptors.first() - if (override.hasComposableAnnotation() != hasComposableAnnotation) { + val overrideFunctionIsComposable = + if (descriptor.isOperator && descriptor.name == OperatorNameConventions.INVOKE) { + override.hasComposableAnnotation() || descriptor.let { + val cls = descriptor.containingDeclaration as? ClassDescriptor + cls?.run { + defaultType.supertypes().any { + it.isFunctionType && + it.arguments.size == descriptor.arity + 1 && + it.hasComposableAnnotation() + } + } ?: false + } + } else { + override.hasComposableAnnotation() + } + if (overrideFunctionIsComposable != hasComposableAnnotation) { context.trace.report( ComposeErrors.CONFLICTING_OVERLOADS.on( declaration, @@ -234,4 +252,9 @@ context.trace.report(COMPOSABLE_VAR.on(name)) } } + + private val FunctionDescriptor.arity get(): Int = + if (extensionReceiverParameter != null) 1 else 0 + + contextReceiverParameters.size + + valueParameters.size }